/*
  author Sylvain Bertrand <digital.ragnarok@gmail.com>
  Protected by GNU Affero GPL v3 with some exceptions.
  See README at root of alga tree.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/slab.h>

#include <alga/alga.h>
#include <alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/edid.h>
#include <alga/amd/atombios/atb.h>
#include <alga/amd/atombios/dce.h>
#include <alga/amd/dce4/dce4.h>
#include <alga/amd/dce4/dce4_dev.h>

#include "dce4.h"
#include "i2c.h"
#include "dpcd.h"

void hpd_init(struct dce4 *dce)
{
	unsigned i;

	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		if ((dce->hpds_used & BIT(i)) == 0) {
			dce->ddev.hpd_off(dce->ddev.dev, i);
			dev_info(dce->ddev.dev, "dce4: HPD%u off\n", i);
			continue;
		}

		dce->ddev.hpd_on(dce->ddev.dev, i);
		dev_info(dce->ddev.dev, "dce4: HPD%u on\n", i);
	}
}

int dce4_hpds_used(struct dce4 *dce, unsigned *hpds_used)
{
	unsigned r;

	lock(dce);
	if (dce->state == DCE4_INVALID)	{
		r = -DCE4_ERR;
		goto unlock;
	}

	*hpds_used = dce->hpds_used;	
	r = 0;

unlock:
	unlock(dce);
	return r;
}
EXPORT_SYMBOL_GPL(dce4_hpds_used);

static int dp_toggle_connected(struct dce4 *dce, unsigned i)
{
	int r;

	r = dp_i2c_adapter_init(dce, i);
	if (r != 0) {
		dev_warn(dce->ddev.dev, "dce4: unable to create i2c adapter "
					"for newly connected dp%u\n", i);
		return r;
	}

	/* dp specs: must query dcpd info before 100 ms */
	r = dpcd_info(dce, i); 
	if (r != 0) {
		dev_warn(dce->ddev.dev, "dce4: unable to query dcpd info for "
						"newly connected dp%u\n", i);
		i2c_del_adapter(&dce->dps[i].i2c_adapter);
		goto err;
	}

	/* init the specs of dp link */
	dp_link_specs_init(dce, i);

	/* get the edid, failing to get it is not fatal */
	r = alga_i2c_edid_fetch(dce->ddev.dev, &dce->dps[i].i2c_adapter,
							&dce->dps[i].edid);
	if (r != 0)
		dev_warn(dce->ddev.dev, "dce4: unable to fetch edid for dp%u\n",
									i);
	r = 0;
err:
	return r;
}

static int dp_toggle(struct dce4 *dce, unsigned i)
{
	int r;
	//DEBUG
	printk(KERN_INFO "DP CONNECTION TOGGLE\n");
	dce->dps[i].connected = !dce->dps[i].connected;	

	r = 0;
	if(dce->dps[i].connected) {
		r = dp_toggle_connected(dce, i);
		if (r != 0) /* restore disconnected state */
			dce->dps[i].connected = false;
	} else {
		i2c_del_adapter(&dce->dps[i].i2c_adapter);
		if (dce->dps[i].edid != NULL) {
			kfree(dce->dps[i].edid);
			dce->dps[i].edid = NULL;
		}
	}
	atb_dp_state(dce->ddev.atb, dce->dps[i].atb_dfp, dce->dps[i].connected);
	return r;
}

static void dp_sink_irq(void)
{
	//DEBUG
	printk(KERN_INFO "DP SINK IRQ RECEIVED\n");
}

int dce4_hpd_intr(struct dce4 *dce, u8 hpd)
{
	int r;
	unsigned i;

	lock(dce);
	if (dce->state == DCE4_INVALID) {	
		r = -DCE4_ERR;
		goto unlock;
	}

	//DEBUG
	printk(KERN_INFO "HPD%u IRQ\n", hpd);
	if ((dce->hpds_used & BIT(hpd)) == 0) {
		dev_warn(dce->ddev.dev, "dce4: intr on unused hpd%u\n, hpd",
									hpd);
		r = 0;
		goto unlock;
	}

	r = 0;
	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		if (dce->dps[i].hpd == hpd) {
			bool sense;

			sense = dce->ddev.hpd_sense(dce->ddev.dev, hpd);
			dce->ddev.hpd_polarity_rearm(dce->ddev.dev, hpd, sense);

			if (dce->dps[i].connected != sense) {
				r = dp_toggle(dce, i);
			} else {
				dp_sink_irq();
			}
			break;
		}
	}
unlock:
	unlock(dce);
	return r;
}
EXPORT_SYMBOL_GPL(dce4_hpd_intr);
